package org.bw.controller;


import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import jdk.nashorn.internal.ir.IfNode;
import org.bw.entity.Movie;
import org.bw.entity.Result;
import org.bw.service.IMovieService;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.example.uploader.autoconfigure.UploadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author hy.wu
 * @since 2022-07-12
 */
@RestController
@RequestMapping("/movie")
public class MovieController {
    @Autowired
    IMovieService movieService;
    @Autowired
    ElasticsearchRestTemplate elasticsearchRestTemplate;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    UploadService uploadService;

    //初始化映射索引文档
    @PostConstruct
    public void init(){
        List<Movie> movieList=movieService.selectMovie();
        boolean b = elasticsearchRestTemplate.indexExists(Movie.class);
        if (b){
            elasticsearchRestTemplate.deleteIndex(Movie.class);
        }
        List<IndexQuery> indexQueryList=new ArrayList<>();
        for (Movie movie : movieList) {
            IndexQuery indexQuery = new IndexQueryBuilder()
                    .withId(movie.getId().toString())
                    .withObject(movie)
                    .build();
            indexQueryList.add(indexQuery);
        }
        elasticsearchRestTemplate.bulkIndex(indexQueryList);
    }

    //es分页列表
    @RequestMapping("/list")
    public AggregatedPage<Movie> list(@RequestParam(defaultValue = "1")int page,
                                      @RequestParam(defaultValue = "3")int size,
                                      Movie movie){
        PageRequest pageRequest = PageRequest.of(page - 1, size, Sort.by(Sort.Order.asc("id")));
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        boolQueryBuilder.must(QueryBuilders.termQuery("deleted",0));
        if (movie.getTid()!=null){
            boolQueryBuilder.must(QueryBuilders.termQuery("tid",movie.getTid()));
        }
        if (StringUtils.hasText(movie.getContent())){
            boolQueryBuilder.must(QueryBuilders.multiMatchQuery(movie.getContent(),
                    "name","contents","dname","aname").field("name").boost(2f));
        }
        if (movie.getStartTime()!=null && movie.getEndTime()!=null){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("created").gte(movie.getStartTime().getTime()).lte(movie.getEndTime().getTime()));
        }
         HighlightBuilder highlightBuilder=new HighlightBuilder();
         HighlightBuilder.Field[] highlightFields=new HighlightBuilder.Field[4];
         highlightBuilder.preTags("<font color='red'>");
         highlightBuilder.postTags("</font>");
         highlightFields[0]=new HighlightBuilder.Field("name");
        highlightFields[1]=new HighlightBuilder.Field("contents");
        highlightFields[2]=new HighlightBuilder.Field("dname");
        highlightFields[3]=new HighlightBuilder.Field("aname");
        SearchQuery searchQuery=new NativeSearchQueryBuilder()
                .withPageable(pageRequest)
                .withQuery(boolQueryBuilder)
                .withHighlightBuilder(highlightBuilder)
                .withHighlightFields(highlightFields)
                .build();
        ObjectMapper objectMapper = new ObjectMapper();
        AggregatedPage<Movie> pageInfo = elasticsearchRestTemplate.queryForPage(searchQuery, Movie.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                ArrayList<T> list = new ArrayList<>();
                SearchHits hits = response.getHits();
                long total = hits.getTotalHits();
                SearchHit[] hits1 = hits.getHits();
                try {
                    for (SearchHit hit : hits1) {
                        Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                        HighlightField name = hit.getHighlightFields().get("name");
                        if (name!=null&&name.getFragments()!=null&&name.getFragments().length>0){
                            sourceAsMap.put("name",name.getFragments()[0].toString());
                        }
                        HighlightField contents = hit.getHighlightFields().get("contents");
                        if (contents!=null&&contents.getFragments()!=null&&contents.getFragments().length>0){
                            sourceAsMap.put("contents",contents.getFragments()[0].toString());
                        }
                        HighlightField dname = hit.getHighlightFields().get("dname");
                        if (dname!=null&&dname.getFragments()!=null&&dname.getFragments().length>0){
                            sourceAsMap.put("dname",dname.getFragments()[0].toString());
                        }
                        HighlightField aname = hit.getHighlightFields().get("aname");
                        if (aname!=null&&aname.getFragments()!=null&&aname.getFragments().length>0){
                            sourceAsMap.put("aname",aname.getFragments()[0].toString());
                        }
                        String s = objectMapper.writeValueAsString(sourceAsMap);
                        T t = objectMapper.readValue(s, clazz);
                        list.add(t);
                    }
                }catch (Exception ex){
                    ex.printStackTrace();
                }
                return new AggregatedPageImpl<>(list,pageable,total);
            }

            @Override
            public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
                return null;
            }
        });
        elasticsearchRestTemplate.refresh(Movie.class);
        return pageInfo;
    }

    //图片上传
    @RequestMapping("/upload")
    public Map<String,Object> upload(MultipartFile file){
        return uploadService.upload(file);
    }

    //添加和更新
    @RequestMapping("/save")
    public Result save(@Valid @RequestBody Movie movie, BindingResult result){
        //JSR303校验
        if (result.hasErrors()){
            return Result.failed(201,"校验不通过");
        }
        //表单重复提交
        String s = SecureUtil.md5(JSONUtil.toJsonStr(movie));
        if (!redisTemplate.opsForValue().setIfAbsent(s,"",5, TimeUnit.SECONDS)){
            return Result.failed(202,"不能重复提交");
        }
        movieService.saveOrUpdateEs(movie);
        return Result.OK();
    }

    @RequestMapping("/delete")
    public Result delete(int id){
        movieService.deleteById(id);
        return Result.OK();
    }
}
